package data

import (
	"context"
	"strconv"
	"time"

	"github.com/google/wire"
	goRedis "github.com/redis/go-redis/v9"
	clientv3 "go.etcd.io/etcd/client/v3"
	"gorm.io/gorm"

	"gitee.com/danqingzhao/go-dango/apps/user/biz"
	"gitee.com/danqingzhao/go-dango/apps/user/conf"
	"gitee.com/danqingzhao/go-dango/pkg/proto/auth"
	"gitee.com/danqingzhao/go-dango/pkg/proto/settings"
	"gitee.com/danqingzhao/go-dango/pkg/third/etcd"
	"gitee.com/danqingzhao/go-dango/pkg/third/orm"
	"gitee.com/danqingzhao/go-dango/pkg/third/redis"
)

// ProviderSet is data providers.
var ProviderSet = wire.NewSet(
	NewData,
	NewPolicyRepo,
	NewPermissionRepo,
	NewMenuRepo,
	NewButtonRepo,
	NewRoleRepo,
	NewUserRepo,
	NewRecordRepo,
)

var (
	httpAddr                  string
	AccessTokenExpireMinutes  time.Duration
	RefreshTokenExpireMinutes time.Duration
	LoginFailLockMinutes      time.Duration
	LoginFailMaxTimes         uint32
)

// Data .
type Data struct {
	Etcd     *clientv3.Client
	Casbin   *auth.CasbinEnforce
	gormDB   *gorm.DB
	redisCli *goRedis.Client
}

// NewData .
func NewData(
	c *settings.ServerConf,
	dataConf *conf.Data,
	secuConf *conf.Security,
	logger *settings.Loggers,
	logHelpers *settings.LogHelpers,
) (*Data, func(), error) {
	httpAddr = c.Http.Host + ":" + strconv.Itoa(int(c.Http.Port))
	logHelper := logHelpers.Data
	dbLogWrite := settings.NewLogger(dataConf.Logger, "database.log")
	db, err := orm.InitGormDB(dataConf.Db, dbLogWrite)
	if err != nil {
		return nil, nil, err
	}
	dbClose := func() {
		logHelper.Info("start close database connection")
		conn, err := db.DB()
		if err != nil {
			logHelper.Error("failed to obtain connection to the underlying database, ", err.Error())
			return
		}
		if err = conn.Close(); err != nil {
			logHelper.Error("database connection closure failed, ", err.Error())
			return
		}
		logHelper.Info("database connection closure completed")
	}
	if err = db.AutoMigrate(
		&biz.PermissionModel{},
		&biz.MenuModel{},
		&biz.ButtonModel{},
		&biz.RoleModel{},
		&biz.CustomerModel{},
		&biz.LoginRecordModel{},
	); err != nil {
		return nil, dbClose, err
	}
	redisCli, err := redis.InitRedis(dataConf.Redis)
	if err != nil {
		return nil, dbClose, err
	}
	redisClose := func() {
		logHelper.Info("start close redis connection")
		if err = redisCli.Close(); err != nil {
			logHelper.Error("redis connection closure failed, ", err.Error())
			return
		}
		logHelper.Info("redis connection closure completed")
	}
	etcdCli, err := etcd.NewEtcdClient(context.Background(), dataConf.Etcd, nil, nil, nil, nil)
	if err != nil {
		logHelper.Error("etcd initialization failed, ", err.Error())
		return nil, func() {
			redisClose()
			dbClose()
		}, err
	}
	ectdClose := func() {
		logHelper.Info("start close etcd connection")
		if err = etcdCli.Close(); err != nil {
			logHelper.Error("etcd connection closure failed, ", err.Error())
			return
		}
		logHelper.Info("etcd connection closure completed")
	}
	enforcer, err := auth.NewCasbinEnforce(etcdCli, dataConf.Casbin, conf.APPName)
	if err != nil {
		logHelper.Error("cashin initialization failed, ", err.Error())
		return nil, func() {
			ectdClose()
			redisClose()
			dbClose()
		}, err
	}
	AccessTokenExpireMinutes = time.Duration(secuConf.AccessTokenExpireMinutes) * time.Minute
	RefreshTokenExpireMinutes = time.Duration(secuConf.RefreshTokenExpireMinutes) * time.Minute
	LoginFailLockMinutes = time.Duration(secuConf.LoginFailLockMinutes) * time.Minute
	LoginFailMaxTimes = secuConf.LoginFailMaxTimes
	data := &Data{gormDB: db, redisCli: redisCli, Casbin: enforcer, Etcd: etcdCli}
	return data, func() {
		ectdClose()
		redisClose()
		dbClose()
	}, nil
}
